from typing import Union, Annotated, Literal
from uuid import UUID

from fastapi import FastAPI

from enum import Enum

"""
--------------------------------------路径参数--------------------------------------
"""

class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"
app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


"""
curl -X 'GET' \
  'http://127.0.0.1:8000/items/123?q=2' \
  -H 'accept: application/json'
  类型是Union[str, None]，表示它可以是一个字符串或None
"""
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}


@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    if model_name is ModelName.alexnet:
        return {"model_name": model_name, "message": "Deep Learning FTW!"}

    if model_name.value == "lenet":
        return {"model_name": model_name, "message": "LeCNN all the images"}

    return {"model_name": model_name, "message": "Have some residuals"}

"""
 包含路径的路径参数¶
假设路径操作的路径为 /files/{file_path}。
但需要 file_path 中也包含路径，比如，home/johndoe/myfile.txt。
此时，该文件的 URL 是这样的：/files/home/johndoe/myfile.txt。
OpenAPI 不支持声明包含路径的路径参数，因为这会导致测试和定义更加困难。
不过，仍可使用 Starlette 内置工具在 FastAPI 中实现这一功能。
路径转换器
直接使用 Starlette 的选项声明包含路径的路径参数：
/files/{file_path:path}
"""
@app.get("/files/{file_path:path}")
async def read_file(file_path:str):
    return {"file_path": file_path}

"""
--------------------------------------查询参数--------------------------------------
"""


fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]

"""
默认值
"""
@app.get("/items/")
async def read_item(skip: int =0,limit: int =10):
    return fake_items_db[skip:skip+limit]

"""
可选参数
把默认值设为 None 即可声明可选的查询参数
"""
@app.get("/items/{item_id}")
async def read_item(item_id:str,q:str | None = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

"""
查询参数类型转换
"""
@app.get("/test/{item_id}")
async def read_item(item_id: str, q: str | None = None, short: bool = False):
    item = {"item_id": item_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is an amazing item that has a long description"}
        )
    return item

"""
多个路径和查询参数
"""
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
    user_id: int, item_id: str, q: str | None = None, short: bool = False
):
    item = {"item_id": item_id, "owner_id": user_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is an amazing item that has a long description"}
        )
    return item

"""
比选查询参数
"""
@app.get("/must/{item_id}")
async def read_user_item(item_id: str,needy:str):
    return {"item_id": item_id, "needy": needy}

"""
必选、默认值、可选
"""
@app.get("/condition/{item_id}")
async def read_user_item(
    item_id: str, needy: str, skip: int = 0, limit: int | None = None
):
    item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
    return item

"""
--------------------------------------请求体--------------------------------------
"""
from pydantic import BaseModel, Field

"""
(BaseModel)表示 Cat 类是从 BaseModel 继承的。
BaseModel 通常来自 pydantic 库，用于创建数据验证和设置类，它能确保类的属性符合指定的数据类型，
并提供一些有用的特性如数据解析、序列化等
"""
class Cat(BaseModel):
    name: str
    age: int
    price: float
    describe: str | None = None

@app.post("/cat")
async def create_cat(cat:Cat):
    return cat

"""
请求体 + 路径参数 + 查询参数¶
"""
@app.put("/cat/{item_id}")
async def update_item(item_id: int, cat: Cat, q: str | None = None):
    result = {"item_id": item_id, **cat.model_dump()}
    if q:
        result.update({"q": q})
    return result

"""
--------------------------------------查询参数和字符串校验--------------------------------------
https://fastapi.tiangolo.com/zh/tutorial/query-params-str-validations/#_3  更多校验方式看这里
"""
from fastapi import Query
@app.get("/check")
async def check(q:Union[str,int] = Query(default=None,alias="query",title="查询参数",description="查询参数",max_length=5,min_length=3)):
    return {"q": q}

"""
get 传入list 1&2&3
"""
@app.get("/checklist/")
async def read_items(q: list = Query(default=[], min_length=3)):
    query_items = {"q": q}
    return query_items

"""
现在假设你不再喜欢此参数。

你不得不将其保留一段时间，因为有些客户端正在使用它，但你希望文档清楚地将其展示为已弃用。

那么将参数 deprecated=True 传入 Query：
"""
@app.get("/deprecated/")
async def deprecated(
    q: Union[str, None] = Query(
        default=None,
        alias="item-query",
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
        max_length=50,
        pattern="^fixedquery$",
        deprecated=True,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

"""
--------------------------------------路径参数和数值校验--------------------------------------
https://fastapi.tiangolo.com/zh/tutorial/path-params-numeric-validations/ 更多校验方式看这里
"""
from fastapi import Path

"""
gt：大于（greater than）
ge：大于等于（greater than or equal）
lt：小于（less than）
le：小于等于（less than or equal）
"""
@app.get("/items/{item_id}")
async def read_items(
    item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
    q: Annotated[str | None, Query(alias="item-query")] = None,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

"""
--------------------------------------查询参数模型--------------------------------------
https://fastapi.tiangolo.com/zh/tutorial/query-param-models/ 更多校验方式看这里
"""

class FilterParams(BaseModel):
    limit: int = Field(100, gt=0, le=100)
    offset: int = Field(0, ge=0)
    order_by: Literal["created_at", "updated_at"] = "created_at"
    tags: list[str] = []


"""
--------------------------------------请求体 - 多个参数--------------------------------------
https://fastapi.tiangolo.com/zh/tutorial/body-multiple-params/ 更多校验方式看这里
"""

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


class User(BaseModel):
    username: str
    full_name: str | None = None
@app.put("/multiple_parameters/{item_id}")
async def multiple_parameters(item_id: int, item: Item, user: User):
    results = {"item_id": item_id, "item": item, "user": user}
    return results

"""
如果你希望它期望一个拥有 item 键并在值中包含模型内容的 JSON，就像在声明额外的请求体参数时所做的那样，
则可以使用一个特殊的 Body 参数 embed：
在这种情况下，FastAPI 将期望像这样的请求体：

item: Item = Body(embed=True)

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    }
}
而不是：


{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2
}
"""

"""
--------------------------------------请求体 - 字段--------------------------------------
https://fastapi.tiangolo.com/zh/tutorial/body-fields/更多校验方式看这里
"""

from typing import Annotated
from fastapi import Body

class Item(BaseModel):
    name: str
    description: str | None = Field(
        default=None, title="The description of the item", max_length=300
    )
    price: float = Field(gt=0, description="The price must be greater than zero")
    tax: float | None = None


@app.put("/update_item/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
    results = {"item_id": item_id, "item": item}
    return results


"""
--------------------------------------请求体 - 嵌套模型--------------------------------------
https://fastapi.tiangolo.com/zh/tutorial/body-nested-models/ 更多方式看这里
"""
from typing import List
class ListTest(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: List[str] = []

@app.put("/List_test/{item_id}")
async def List_test(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

class Image(BaseModel):
    url:str
    name:str

class Device(BaseModel):
    name:str
    code:int
    image:Image | None = None

@app.put("/device/{item_id}")
async def device(item_id:int,device:Device):
    results = {"item_id": item_id, "device": device}
    return results


"""
--------------------------------------额外数据类型--------------------------------------
https://fastapi.tiangolo.com/zh/tutorial/extra-data-types/ 更多方式看这里
"""

"""
UUID:
一种标准的 "通用唯一标识符" ，在许多数据库和系统中用作ID。
在请求和响应中将以 str 表示。
datetime.datetime:
一个 Python datetime.datetime.
在请求和响应中将表示为 ISO 8601 格式的 str ，比如: 2008-09-15T15:53:00+05:00.
datetime.date:
Python datetime.date.
在请求和响应中将表示为 ISO 8601 格式的 str ，比如: 2008-09-15.
datetime.time:
一个 Python datetime.time.
在请求和响应中将表示为 ISO 8601 格式的 str ，比如: 14:23:55.003.
datetime.timedelta:
一个 Python datetime.timedelta.
在请求和响应中将表示为 float 代表总秒数。
Pydantic 也允许将其表示为 "ISO 8601 时间差异编码", 查看文档了解更多信息。
frozenset:
在请求和响应中，作为 set 对待：
在请求中，列表将被读取，消除重复，并将其转换为一个 set。
在响应中 set 将被转换为 list 。
产生的模式将指定那些 set 的值是唯一的 (使用 JSON 模式的 uniqueItems)。
bytes:
标准的 Python bytes。
在请求和响应中被当作 str 处理。
生成的模式将指定这个 str 是 binary "格式"。
Decimal:
标准的 Python Decimal。
在请求和响应中被当做 float 一样处理。
您可以在这里检查所有有效的pydantic数据类型: Pydantic data types.
"""
from datetime import datetime, time, timedelta
from uuid import UUID
@app.put("/types_test/{item_id}")
async def types_test(
    item_id: UUID,
    start_datetime: Annotated[datetime, Body()],
    end_datetime: Annotated[datetime, Body()],
    process_after: Annotated[timedelta, Body()],
    repeat_at: Annotated[time | None, Body()] = None,
):
    start_process = start_datetime + process_after
    duration = end_datetime - start_process
    return {
        "item_id": item_id,
        "start_datetime": start_datetime,
        "end_datetime": end_datetime,
        "process_after": process_after,
        "repeat_at": repeat_at,
        "start_process": start_process,
        "duration": duration,
    }